﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;

namespace System.Collections.Generic
{
  /// <summary>Helper extension methods for fast use of collections.</summary>
  internal static class WebAPICollectionExtensions
  {
    /// <summary>Return a new array with the value added to the end. Slow and best suited to long lived arrays with few writes relative to reads.</summary>
    internal static T[] AppendAndReallocate<T>(this T[] array, T value)
    {
      Contract.Assert(array != null);

      int originalLength = array.Length;
      T[] newArray = new T[originalLength + 1];
      array.CopyTo(newArray, 0);
      newArray[originalLength] = value;
      return newArray;
    }

    /// <summary>Return the enumerable as an Array, copying if required. Optimized for common case where it is an Array. 
    /// Avoid mutating the return value.</summary>
    internal static T[] AsArray<T>(this IEnumerable<T> values)
    {
      Contract.Assert(values != null);

      T[] array = values as T[];
      if (array == null)
      {
        array = values.ToArray();
      }
      return array;
    }

    /// <summary>Return the enumerable as a Collection of T, copying if required. Optimized for the common case where it is 
    /// a Collection of T and avoiding a copy if it implements IList of T. Avoid mutating the return value.</summary>
    internal static Collection<T> AsCollection<T>(this IEnumerable<T> enumerable)
    {
      Contract.Assert(enumerable != null);

      Collection<T> collection = enumerable as Collection<T>;
      if (collection != null)
      {
        return collection;
      }
      // Check for IList so that collection can wrap it instead of copying
      IList<T> list = enumerable as IList<T>;
      if (list == null)
      {
        list = new List<T>(enumerable);
      }
      return new Collection<T>(list);
    }

    /// <summary>Return the enumerable as a IList of T, copying if required. Avoid mutating the return value.</summary>
    internal static IList<T> AsIList<T>(this IEnumerable<T> enumerable)
    {
      Contract.Assert(enumerable != null);

      IList<T> list = enumerable as IList<T>;
      if (list != null)
      {
        return list;
      }
      return new List<T>(enumerable);
    }

    /// <summary>Return the enumerable as a List of T, copying if required. Optimized for common case where it is an List of T 
    /// or a ListWrapperCollection of T. Avoid mutating the return value.</summary>
    internal static List<T> AsList<T>(this IEnumerable<T> enumerable)
    {
      Contract.Assert(enumerable != null);

      List<T> list = enumerable as List<T>;
      if (list != null)
      {
        return list;
      }
      ListWrapperCollection<T> listWrapper = enumerable as ListWrapperCollection<T>;
      if (listWrapper != null)
      {
        return listWrapper.ItemsList;
      }
      return new List<T>(enumerable);
    }

    /// <summary>Remove values from the list starting at the index start.</summary>
    internal static void RemoveFrom<T>(this List<T> list, int start)
    {
      Contract.Assert(list != null);
      Contract.Assert(start >= 0 && start <= list.Count);

      list.RemoveRange(start, list.Count - start);
    }

    /// <summary>Return the only value from list, the type's default value if empty, or call the errorAction for 2 or more.</summary>
    internal static T SingleDefaultOrError<T, TArg1>(this IList<T> list, Action<TArg1> errorAction, TArg1 errorArg1)
    {
      Contract.Assert(list != null);
      Contract.Assert(errorAction != null);

      switch (list.Count)
      {
        case 0:
          return default(T);

        case 1:
          T value = list[0];
          return value;

        default:
          errorAction(errorArg1);
          return default(T);
      }
    }

    /// <summary>Returns a single value in list matching type TMatch if there is only one, null if there are none of type TMatch or calls the
    /// errorAction with errorArg1 if there is more than one.</summary>
    internal static TMatch SingleOfTypeDefaultOrError<TInput, TMatch, TArg1>(this IList<TInput> list, Action<TArg1> errorAction, TArg1 errorArg1) where TMatch : class
    {
      Contract.Assert(list != null);
      Contract.Assert(errorAction != null);

      TMatch result = null;
      for (int i = 0; i < list.Count; i++)
      {
        TMatch typedValue = list[i] as TMatch;
        if (typedValue != null)
        {
          if (result == null)
          {
            result = typedValue;
          }
          else
          {
            errorAction(errorArg1);
            return null;
          }
        }
      }
      return result;
    }

    /// <summary>Convert an ICollection to an array, removing null values. Fast path for case where there are no null values.</summary>
    internal static T[] ToArrayWithoutNulls<T>(this ICollection<T> collection) where T : class
    {
      Contract.Assert(collection != null);

      T[] result = new T[collection.Count];
      int count = 0;
      foreach (T value in collection)
      {
        if (value != null)
        {
          result[count] = value;
          count++;
        }
      }
      if (count == collection.Count)
      {
        return result;
      }
      else
      {
        T[] trimmedResult = new T[count];
        Array.Copy(result, trimmedResult, count);
        return trimmedResult;
      }
    }

    /// <summary>Convert the array to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for array input.</summary>
    internal static Dictionary<TKey, TValue> ToDictionaryFast<TKey, TValue>(this TValue[] array, Func<TValue, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
      Contract.Assert(array != null);
      Contract.Assert(keySelector != null);

      Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>(array.Length, comparer);
      for (int i = 0; i < array.Length; i++)
      {
        TValue value = array[i];
        dictionary.Add(keySelector(value), value);
      }
      return dictionary;
    }

    /// <summary>Convert the list to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for IList of T input with fast path for array.</summary>
    internal static Dictionary<TKey, TValue> ToDictionaryFast<TKey, TValue>(this IList<TValue> list, Func<TValue, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
      Contract.Assert(list != null);
      Contract.Assert(keySelector != null);

      TValue[] array = list as TValue[];
      if (array != null)
      {
        return ToDictionaryFast(array, keySelector, comparer);
      }
      return ToDictionaryFastNoCheck(list, keySelector, comparer);
    }

    /// <summary>Convert the enumerable to a Dictionary using the keySelector to extract keys from values and the specified comparer. Fast paths for array and IList of T.</summary>
    internal static Dictionary<TKey, TValue> ToDictionaryFast<TKey, TValue>(this IEnumerable<TValue> enumerable, Func<TValue, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
      Contract.Assert(enumerable != null);
      Contract.Assert(keySelector != null);

      TValue[] array = enumerable as TValue[];
      if (array != null)
      {
        return ToDictionaryFast(array, keySelector, comparer);
      }
      IList<TValue> list = enumerable as IList<TValue>;
      if (list != null)
      {
        return ToDictionaryFastNoCheck(list, keySelector, comparer);
      }
      Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>(comparer);
      foreach (TValue value in enumerable)
      {
        dictionary.Add(keySelector(value), value);
      }
      return dictionary;
    }

    /// <summary>Convert the list to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for IList of T input. No checking for other types.</summary>
    private static Dictionary<TKey, TValue> ToDictionaryFastNoCheck<TKey, TValue>(IList<TValue> list, Func<TValue, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
      Contract.Assert(list != null);
      Contract.Assert(keySelector != null);

      int listCount = list.Count;
      Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>(listCount, comparer);
      for (int i = 0; i < listCount; i++)
      {
        TValue value = list[i];
        dictionary.Add(keySelector(value), value);
      }
      return dictionary;
    }
  }
}
